/*
 * Albareth - an action roleplaying game.
 * Copyright (C) 2008 Jens Anuth
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 *   02110-1301, USA.
 * 
 * $Id: AngbandDungeonGenerator.java 46 2008-05-15 18:09:39Z jens464 $
*/
package albareth.generator;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.Vector;

import albareth.core.NoLocationAvailableException;
import albareth.core.entity.Item;
import albareth.core.entity.ItemFactory;
import albareth.core.entity.item.Door;
import albareth.frontend.TileCollection;
import albareth.util.Location;
import frosch.util.Coordinates;
import frosch.util.Direction;
import frosch.util.FToolkit;
import frosch.util.IntMap2D;
import frosch.util.Log;

/**
 * Generates angband-style room mazes.
 */
public class AngbandDungeonGenerator extends MapGenerator
{
    private static final int CELL_WALL = 0;
    private static final int CELL_SEPARATED = 1;
    private static final int CELL_CONNECTED = 2;
    
    private int _roomCellWidth;
    private int _roomCellHeight;
    
    private ArrayList _roomsSeparated;
    private ArrayList _roomsConnected;
    private ArrayList _doorLocations;
    
    int[][] _floodFillMap;
    
    
    public AngbandDungeonGenerator(int roomCellWidth, int roomCellHeight)
    {
        _roomCellWidth = roomCellWidth;
        _roomCellHeight = roomCellHeight;
    }

    protected void generateMap(int width, int height) throws
            MapGenerationFailureException
    {
        MapGeneratorTools.fillMapRect(map, 0, 0, width, height, DEFAULT_WALL);
        _floodFillMap = new int[map.getWidth()][map.getHeight()];
        
        _roomsSeparated = new ArrayList();
        _roomsConnected = new ArrayList();
        
        _doorLocations = new ArrayList();
        
        int cellsHoriz = (int)Math.floor((float)width / _roomCellWidth);
        int cellsVert = (int)Math.floor((float)height / _roomCellHeight);
        
        for (int i = 0; i < cellsHoriz; i++)
        {
            for (int j = 0; j < cellsVert; j++)
            {
                _roomsSeparated.add(
                        createRandomRoom(
                                i * _roomCellWidth + 1, j * _roomCellHeight + 1,
                                _roomCellWidth - 2, _roomCellHeight - 2
                        )
                );
            }
        }
        
        connectRooms();
        
        placeDoors();
        
        try
        {
            Room startingRoom = (Room)_roomsConnected.get(0);
            //staring room has no mobiles but double item spawn
            startingRoom.removeAllMobiles();
            startingRoom.fillWithItemSpawn();
            entranceLocation = startingRoom.getRandomLocation();


            exitLocation =
                ((Room)_roomsConnected.get(
                        _roomsConnected.size() - 1)).getRandomLocation();
            
        } catch (NoLocationAvailableException e) {
            throw new MapGenerationFailureException(
                    "No location available for stairs up/down!", e);
        }

    }

    private void placeDoors()
    {
        for (Iterator it = _doorLocations.iterator(); it.hasNext(); )
        {
            Location doorLoc = (Location)it.next();
            Location north = doorLoc.getNeighborLocation(Direction.NORTH);
            Location south = doorLoc.getNeighborLocation(Direction.SOUTH);
            Location east = doorLoc.getNeighborLocation(Direction.EAST);
            Location west = doorLoc.getNeighborLocation(Direction.WEST);
            
            if ((north.getGroundType() != null &&
                 south.getGroundType() != null &&
                 north.getGroundType().isWall() &&
                 south.getGroundType().isWall())   ||
                (east.getGroundType() != null &&
                 west.getGroundType() != null &&
                 east.getGroundType().isWall() &&
                 west.getGroundType().isWall()))
            {
                Item door = new Door(
                        "Door",
                        TileCollection.DUNGEON_DOOR_CLOSED,
                        TileCollection.DUNGEON_DOOR_OPEN,
                        false
                );
                door.moveTo(doorLoc);
            }
        }
    }

    private Room createRandomRoom(int minX, int minY, int maxW, int maxH)
    {
        int w = FToolkit.rand(2, maxW);
        int h = FToolkit.rand(2, maxH);
        int x = FToolkit.rand(minX, minX + maxW - w);
        int y = FToolkit.rand(minY, minY + maxH - h);
        Room room = new Room(x, y, w, h, map);
        room.drawSelf();
        room.fillWithItemSpawn();
        room.fillWithMonsterSpawn();
        IntMap2D.fillRect(_floodFillMap, x, y, w, h, CELL_SEPARATED);
        
        return room;
    }
    
    private void connectRooms()
    {
        Room firstRoom = (Room)_roomsSeparated.remove(
                FToolkit.rand(_roomsSeparated.size())
        );

        _roomsConnected.add(firstRoom);
        
        IntMap2D.fillRect(
                _floodFillMap,
                firstRoom.x, firstRoom.y,
                firstRoom.w, firstRoom.h,
                CELL_CONNECTED
        );

        
        while (!_roomsSeparated.isEmpty())
        {
            //pick one seperated room and a location within it.
            Room roomStart = (Room)_roomsSeparated.get(
                    FToolkit.rand(_roomsSeparated.size())
            );
            Coordinates corridorStart = roomStart.getRandomCoordinates();

            //pick one location out of the already connected rooms.
            Room roomEnd = (Room)_roomsConnected.get(
                    FToolkit.rand(_roomsConnected.size())
            );
            Coordinates corridorEnd = roomEnd.getRandomCoordinates();
            
            //build corridor between these two locations
            buildCorridor(corridorStart, corridorEnd);

            //floodfill already connected dungeon parts:
            IntMap2D.floodFill(_floodFillMap, corridorStart, CELL_CONNECTED, CELL_WALL);

            
            //roomEnd now is connected to the others.
            _roomsSeparated.remove(roomStart);
            _roomsConnected.add(roomStart);
        }
        
    }


    private void buildCorridor(Coordinates start, Coordinates end)
    {
        /* have we left our starting room yet? */
        boolean startRoomLeft = false;
        /* true: move horizontal. false: move vertical */
        boolean moveDirectionToggle = FToolkit.flipCoin();
        int currentX = start.x;
        int currentY = start.y;
        
        while (currentX != end.x || currentY != end.y)
        {
            int dx = end.x - currentX;
            int dy = end.y - currentY;
            
            if ((dx != 0 && moveDirectionToggle) || dy == 0)
            {
                currentX += dx > 0 ? 1 : -1;
            } else if (dy != 0) {
                currentY += dy > 0 ? 1 : -1;
            }
            
            //did we reach a part of the already interconnected dungeon? 
            if (_floodFillMap[currentX][currentY] == CELL_CONNECTED) {
                return;
            }

            //time for a corridor direction change in next iteration?
            if (Math.random() < .3 && startRoomLeft) {
                moveDirectionToggle = !moveDirectionToggle;
            }

            map.setGround(DEFAULT_FLOOR, currentX, currentY);

            //did we leave starting room? then schedule building a door here.
            if (_floodFillMap[currentX][currentY] == CELL_WALL &&
                    !startRoomLeft)
            {
                _doorLocations.add(new Location(currentX, currentY, map));
                startRoomLeft = true;
            }
            
            _floodFillMap[currentX][currentY] = CELL_SEPARATED;
        }
        
    }


}
